/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.staf.SnmpTopologyExplorer;

import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderAdapter;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.TreeMap;
import java.util.GregorianCalendar;
import java.util.Locale;
import jxl.write.WriteException;

import ru.staf.SnmpTopologyExplorer.V2.TopologyModeler;
import ru.staf.SnmpTopologyExplorer.V2.Switch;

/**
 *
 * @author aezhov
 */
public class ConfigParser extends XMLReaderAdapter {
    static final String FS_SEPARATOR = System.getProperty("file.separator");
    
    enum VariantType {

        snmpwalkOutput, snmpGetNext
    };

    class Variant {
        String name;
        VariantType type;
        boolean numericOids;
        String version;
        String fsDirectory;
    }

    class SwitchParams {

        String name;
        Variant variant;
        String filename;
        String networkAddress;
        String port;
        NetScanner scanner;
    }
    String fileName = null;

    enum Section {

        none, netInfo, snmpVariant, smartSwitch, report, netScanner
    };

    enum Stage {

        none, start, version, options, numericOids, fsDirectory, snmpData, fileName, 
        fileNameScanner, fsDirectoryScanner, networkAddress, port, fsDirectoryReport,
        filePrefix, addDate, addTime
    };
    Section currentSection = Section.none;
    NetScanner currentScanner = null;
    GenericAgent currentAgent = null;
    SwitchParams currentSwitch = null;
    Stage currentStage = Stage.none;
    Variant currentVariant = null;
    TopologyModeler modeler = new TopologyModeler();
    TreeMap<String, Variant> variants = new TreeMap<String, Variant>();
    TreeMap<String, SwitchParams> smartSwitchDefs = new TreeMap<String, SwitchParams>();
    TreeMap<String, NetScanner> scanners = new TreeMap<String, NetScanner>();

    String filenameDir = null;
    String filename = null;
    String datePrefix = null;
    String timePrefix = null;
    
    public ConfigParser(String filePath) throws org.xml.sax.SAXException,
            java.io.FileNotFoundException, java.io.IOException {
        File file = new File(filePath);
        FileInputStream fis = new FileInputStream(file);
        InputSource xmlIS = new InputSource(fis);
        parse(xmlIS);
    }

    public void dumpCurrentVariant() {
        System.out.println("== Variant created wit name " + currentVariant.name);
        System.out.println("  Type: " + currentVariant.type);
        System.out.println("  Version: " + currentVariant.version);
        System.out.println("  Use numeric OIDs: " + currentVariant.numericOids);
    }

    public void startElement(String uri, String localName, String qName,
            Attributes attrs) throws org.xml.sax.SAXException {
        String elementName = qName.toString();
        if (elementName.equals("netInfo") && currentSection == Section.none) {
            currentSection = Section.netInfo;
        } else if (elementName.equals("snmpVariant") && currentSection == Section.netInfo) {
            currentSection = Section.snmpVariant;
            currentStage = Stage.start;
        } else if (elementName.equals("smartSwitch") && currentSection == Section.netInfo) {
            currentSection = Section.smartSwitch;
            currentStage = Stage.start;
        } else if (elementName.equals("netScanner") && currentSection == Section.netInfo) {
            currentSection = Section.netScanner;
            currentStage = Stage.start;
        } else if (elementName.equals("report") && currentSection == Section.netInfo) {
            currentSection = Section.report;
            currentStage = Stage.start;
        }

        try {
            switch (currentSection) {
                case snmpVariant:
                    snmpVariant(qName, attrs);
                    break;
                case smartSwitch:
                    smartSwitch(qName, attrs);
                    break;
                case netScanner:
                    netScanner(qName, attrs);
                    break;
                case report:
                    report(qName, attrs);
                    break;
            }
        } catch (ConfigurationException exc) {
            System.out.println("FATAL configuration error " + exc);
            System.exit(exc.errorCode);
        }
    }

    public void dumpCurrentSmartswitch() {
        System.out.println("=== Creating switch " + currentSwitch.name);
        System.out.println("  SNMP Variant: " + currentSwitch.variant.name);
        System.out.println("  SNMP Type: " + currentSwitch.variant.type);

        try {
            if (currentSwitch.variant.type == VariantType.snmpwalkOutput) {
                String dir = currentSwitch.variant.fsDirectory;
                FilePseudoAgent agent = new FilePseudoAgent(dir + FS_SEPARATOR
                        + currentSwitch.filename);
                Switch sw = new Switch(currentSwitch.name, agent, currentSwitch.scanner);
                modeler.addSwitch(sw);
            } else if (currentSwitch.variant.type == VariantType.snmpGetNext) {
            }
        } catch (ConfigurationException exc) {
            System.out.println("Configuration exception: " + exc);
            System.exit(exc.errorCode);
        } catch (ProcessException exc) {
            System.out.println("ProcessException: " + exc);
            System.exit(exc.error);
        } catch (java.io.IOException exc) {
            System.out.println("IOException: " + exc);
            System.exit(Errors.STD_EXCEPTION_IOEXCEPTION);
        }
    }

    public void endElement(String uri, String localName, String qName) {
        switch (currentSection) {
            case report:
                switch (currentStage) {
                    case none:
                        currentSection = Section.netInfo;
                        currentStage = Stage.start;
                        break;
                    case fsDirectoryReport:
                    case filePrefix:
                    case addDate:
                    case addTime:
                        currentStage = Stage.none;
                        break;
                }
                break;
            case netScanner:
                switch (currentStage) {
                    case none:
                        currentSection = Section.netInfo;
                        currentStage = Stage.start;
                        break;
                    case fsDirectoryScanner:
                        currentStage = Stage.none;
                        break;
                    case fileNameScanner:
                        currentStage = Stage.none;
                        break;
                }
                break;
            case smartSwitch:
                switch (currentStage) {
                    case none:
                        currentSection = Section.netInfo;
                        currentStage = Stage.start;
                        dumpCurrentSmartswitch();
                        break;
                    case snmpData:
                        currentStage = Stage.none;
                        break;
                    case fileName:
                        currentStage = Stage.snmpData;
                        break;
                }
                break;
            case snmpVariant:
                switch (currentStage) {
                    case none:
                        currentSection = Section.netInfo;
                        currentStage = Stage.start;
                        dumpCurrentVariant();
                        break;
                    case version:
                    case options:
                        currentStage = Stage.none;
                        break;
                    case numericOids:
                    case fsDirectory:
                        currentStage = Stage.options;
                        break;
                }
        }
    }

    public void endDocument() {
        System.out.print("=== Config file is out. Geneating file ");
        if (filenameDir != null)
            filename = filenameDir + FS_SEPARATOR + filename;
        if (datePrefix != null)
            filename = filename + "_" + datePrefix;
        if (timePrefix != null)
            filename = filename + "_" + timePrefix;

        filename += ".xls";
        System.out.println(filename);

        modeler.setFilename(filename);
        try {
            modeler.calculateCrossSwitchRelations();
        } catch (java.io.IOException exc) {
            System.out.println("Report processing error: IO exception " + exc);
            System.exit(Errors.STD_EXCEPTION_IOEXCEPTION);
        } catch (jxl.write.WriteException exc) {
            System.out.println("Report processing error: Excel writw excption " + exc);
            System.exit(Errors.PROCESS_EXCEL_WRITE_ERROR);
        }
    }

    public void characters(char[] ch, int start, int length) throws SAXException {

        GregorianCalendar calendar = new GregorianCalendar();
        String cdata = new String(ch, start, length);

        if (cdata == null) {
            System.out.println("Configuration error: undefined or incorrect SNMP version");
            System.exit(Errors.CONFIG_UNDEFINED_VERSION);
        }

        switch (currentStage) {
            case version:
                currentVariant.version = cdata;
                break;
            case numericOids:
                if (cdata.equals("true")) {
                    currentVariant.numericOids = true;
                } else if (cdata.equals("false")) {
                    currentVariant.numericOids = false;
                }
                break;
            case fsDirectory:
                currentVariant.fsDirectory = cdata;
                break;
            case fileName:
                currentSwitch.filename = cdata;
                break;
            case fsDirectoryScanner:
                currentScanner.setFsDirectory(cdata);
                break;
            case fileNameScanner:
                try {
                    currentScanner.add(cdata);
                } catch (ConfigurationException exc) {
                    System.out.println("Configuration exception: scanner file addition error");
                    System.exit(exc.errorCode);
                } catch (java.io.IOException exc) {
                    System.out.println("Configuration exception: IO exception during file addition");
                    System.exit(Errors.STD_EXCEPTION_IOEXCEPTION);
                }
                break;
            case fsDirectoryReport:
                filenameDir = cdata;
                break;
            case filePrefix:
                filename = cdata;
                break;
            case addDate:
                if (cdata.equals("true")) {
                    datePrefix = new Integer(calendar.get(GregorianCalendar.DAY_OF_MONTH)).toString() + "_" +
                                 new Integer(calendar.get(GregorianCalendar.MONTH)).toString() + "_" +
                                 new Integer(calendar.get(GregorianCalendar.YEAR)).toString();
                } else if (cdata.equals("false")) {

                }
            case addTime:
                if (cdata.equals("true")) {
                    timePrefix = new Integer(calendar.get(GregorianCalendar.HOUR_OF_DAY)).toString() + "_" +
                                 new Integer(calendar.get(GregorianCalendar.MINUTE)).toString();
                } else if (cdata.equals("false")) {

                }
                break;
        }
    }

    // Top level element parsing methods
    public void snmpVariant(String qName, Attributes attrs) throws ConfigurationException {
        switch (currentStage) {
            case start:
                String type = attrs.getValue("type");
                if (type == null) {
                    System.out.println("Configuration error: snmpVariant has not type attribute");
                    throw new ConfigurationException(Errors.CONFIG_XML_SNMP_TYPE_UNKNOWN);
                }

                currentVariant = new Variant();
                if (type.equals("snmpwalkOutput")) {
                    currentVariant.type = VariantType.snmpwalkOutput;
                } else if (type.equals("snmpGetNext")) {
                    currentVariant.type = VariantType.snmpGetNext;
                } else {
                    throw new ConfigurationException(Errors.CONFIG_XML_SNMP_TYPE_UNKNOWN);
                }

                String name = attrs.getValue("name");
                if (name == null) {
                    System.out.println("Configuration error: name is not defined for variant");
                    throw new ConfigurationException(Errors.CONFIG_UNDEFINED_VARIANT_NAME);
                }

                variants.put(name, currentVariant);
                currentVariant.name = name;
                currentStage = Stage.none;
                break;

            case none:
                if (qName.equals("version")) {
                    currentStage = Stage.version;
                } else if (qName.equals("options")) {
                    currentStage = Stage.options;
                }
                break;

            case options:
                if (qName.equals("numericOids")) {
                    currentStage = Stage.numericOids;
                } else if (qName.equals("fsDirectory")) {
                    currentStage = Stage.fsDirectory;
                }
                break;
        }
    }

    public void smartSwitch(String qName, Attributes attrs) {
        switch (currentStage) {
            case start:
                String switchName = attrs.getValue("name");
                String variantName = attrs.getValue("snmpVariant");
                String netScanner = attrs.getValue("netScanner");
                
                if (switchName == null) {
                    System.out.println("Configuration error: switch name is not defined ");
                    System.exit(Errors.CONFIG_UNDEFINED_NAME);
                }

                if (smartSwitchDefs.containsKey(switchName)) {
                    System.out.println("Configuration error: switch name " + switchName + " already defined");
                    System.exit(Errors.CONFIG_NAME_REDEFINITION);
                }

                currentSwitch = new SwitchParams();
                currentSwitch.name = switchName;

                if (variantName == null) {
                    System.out.println("Configuration error: snmpVariant attribute is not defined ");
                    System.exit(Errors.CONFIG_UNDEFINED_VARIANT_ATTRIBUTE);
                }
                currentVariant = variants.get(variantName);
                if (currentVariant == null) {
                    System.out.println("Configuration error: snmp variant hasn't been defined");
                    System.exit(Errors.CONFIG_UNDEFINED_VARIANT_ATTRIBUTE);
                }

                if (netScanner == null) {
                    System.out.println("Configuration error: netScanner is not defined");
                    System.exit(Errors.CONFIG_NETSCANNER_UNDEFINED);
                }
                currentScanner = scanners.get(netScanner);
                if (currentScanner == null) {
                    System.out.println("Configuration error: netScanner is not defined");
                    System.exit(Errors.CONFIG_NETSCANNER_UNDEFINED);
                }

                currentSwitch.scanner = currentScanner;
                currentSwitch.variant = currentVariant;
                currentStage = Stage.none;

                break;
            case none:
                if (qName.equals("snmpData")) {
                    currentStage = Stage.snmpData;
                }
                break;
            case snmpData:
            case fileName:
                if (currentVariant.type == VariantType.snmpwalkOutput) {
                    snmpwalkOutputDataParser(qName, attrs);
                } else if (currentVariant.type == VariantType.snmpGetNext) {
                    snmpGetNextDataParser(qName, attrs);
                }
                break;
        }
    }

    public void snmpwalkOutputDataParser(String qName, Attributes attrs) {
        if (qName.equals("fileName")) {
            currentStage = Stage.fileName;
        }
    }

    public void snmpGetNextDataParser(String qName, Attributes attrs) {

    }

    public void netScanner(String qName, Attributes attrs) {
        switch (currentStage) {
            case start:
                String type = attrs.getValue("type");
                String name = attrs.getValue("name");
                if (type == null) {
                    System.out.println("Configuration error: netScanner type isn't defined");
                    System.exit(Errors.CONFIG_UNDEFINED_TYPE);
                }
                if (name == null) {
                    System.out.println("Configuration error: netScanner name isn't defined");
                    System.exit(Errors.CONFIG_UNDEFINED_NAME);
                }

                if (type.equals("nmapXMLoutput")) {
                    try {
                        currentScanner = new NetScanner();
                    } catch (org.xml.sax.SAXException exc) {

                    }
                    scanners.put(name, currentScanner);
                }
                currentStage = Stage.none;
                break;
            case none:
                if (qName.equals("fsDirectory")) {
                    currentStage = Stage.fsDirectoryScanner;
                } else if (qName.equals("fileName")) {
                    currentStage = Stage.fileNameScanner;
                }
                break;
        }
    }

    public void report(String qName, Attributes attrs) {
        switch (currentStage) {
            case start:
                currentStage = Stage.none;
                break;
            case none:
                if (qName.equals("fsDirectory")) {
                    currentStage = Stage.fsDirectoryReport;
                } else if (qName.equals("filePrefix")) {
                    currentStage = Stage.filePrefix;
                } else if (qName.equals("addDate")) {
                    currentStage = Stage.addDate;
                } else if (qName.equals("addTime")) {
                    currentStage = Stage.addDate;
                }
                break;
        }
    }
}
